ALBのアクセスをCloudFrontからだけに許可するように設定してみた
今までALBの前にCloudFrontを置いた設定を行ったことが無かったのでやってみました。
やり方
CloudFront + S3の場合はOAIやOACという設定をCloudFrontで行い、S3バケットポリシーを設定して制限できました。
ALBの場合は以下の方法があります。
- CloudFrontでカスタムヘッダーをつけてALBで制限する方法
- セキュリティグループにCloudFrontのマネージドプレフィックスを設定する方法
CloudFrontでカスタムヘッダーをつけてALBで制限する方法
まずはCloudFrontとALBを作成していきます。
VPCとパブリックサブネットは作成済みのものを使用しています。
今回はCloudFormationを使用してCloudFront、ALB、EC2を作成してみました。
AWSTemplateFormatVersion: "2010-09-09" Description: CloudFront ALB EC2 Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# VolumeSize: Default: 8 Type: Number Ec21InstanceType: Default: t2.micro Type: String Vpcid: Type: AWS::EC2::VPC::Id Description: Enter VPC ID PublicSubnet1: Type: AWS::EC2::Subnet::Id Description: Enter Subnet ID PublicSubnet2: Type: AWS::EC2::Subnet::Id Description: Enter Subnet ID Resources: # ------------------------------------------------------------# # IAM # ------------------------------------------------------------# Ec2SsmRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore RoleName: EC2SsmRole Ec2IamInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: Ec2InstanceProfile Roles: - !Ref Ec2SsmRole # ------------------------------------------------------------# # Security Group # ------------------------------------------------------------# AlbSg: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for ALB GroupName: alb-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - CidrIp: 0.0.0.0/0 FromPort: 80 IpProtocol: tcp ToPort: 80 Tags: - Key: Name Value: alb-sg VpcId: !Ref Vpcid Ec2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for EC2 GroupName: ec2-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - FromPort: 80 IpProtocol: tcp ToPort: 80 SourceSecurityGroupId: !Ref AlbSg Tags: - Key: Name Value: ec2-sg VpcId: !Ref Vpcid # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# Ec2: Type: AWS::EC2::Instance Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: true Iops: 3000 VolumeSize: !Ref VolumeSize VolumeType: gp3 IamInstanceProfile: !Ref Ec2IamInstanceProfile ImageId: ami-0f36dcfcc94112ea1 InstanceType: !Ref Ec21InstanceType NetworkInterfaces: - AssociatePublicIpAddress: true DeleteOnTermination: true DeviceIndex: 0 GroupSet: - !Ref Ec2Sg SubnetId: !Ref PublicSubnet1 Tags: - Key: Name Value: ec2 UserData: !Base64 | #!/bin/bash yum update -y yum upgrade -y yum install httpd -y systemctl enable httpd systemctl start httpd touch /var/www/html/index.html echo "CloudFront -> ALB -> EC2" > /var/www/html/index.html # ------------------------------------------------------------# # ALB # ------------------------------------------------------------# Alb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 Name: alb Scheme: internet-facing SecurityGroups: - !Ref AlbSg Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 Tags: - Key: Name Value: alb Type: application TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckEnabled: true HealthCheckIntervalSeconds: 30 HealthCheckPath: / HealthCheckPort: 80 HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 5 IpAddressType: ipv4 Matcher: HttpCode: 200 Name: targetgroup Port: 80 Protocol: HTTP ProtocolVersion: HTTP1 Tags: - Key: Name Value: targetgroup Targets: - Id: !Ref Ec2 Port: 80 TargetType: instance UnhealthyThresholdCount: 2 VpcId: !Ref Vpcid AlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - FixedResponseConfig: ContentType: text/plain MessageBody: Access denied StatusCode: 403 Type: fixed-response LoadBalancerArn: !Ref Alb Port: 80 Protocol: HTTP AlbListenerRule: Type: AWS::ElasticLoadBalancingV2::ListenerRule Properties: Actions: - TargetGroupArn: !Ref TargetGroup Type: forward Conditions: - Field: http-header HttpHeaderConfig: HttpHeaderName: Custom-Header Values: - kobayashi-riku0226 ListenerArn: !Ref AlbListener Priority: 1 # ------------------------------------------------------------# # CloudFront # ------------------------------------------------------------# CloudFront: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: DefaultCacheBehavior: AllowedMethods: - GET - HEAD CachedMethods: - GET - HEAD CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 TargetOriginId: Alb ViewerProtocolPolicy: allow-all Enabled: True HttpVersion: http1.1 Origins: - CustomOriginConfig: HTTPPort: 80 OriginProtocolPolicy: http-only DomainName: !GetAtt Alb.DNSName Id: Alb OriginCustomHeaders: - HeaderName: Custom-Header HeaderValue: kobayashi-riku0226 PriceClass: PriceClass_200 Outputs: # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# CloudFrontDomain: Value: !GetAtt CloudFront.DomainName Export: Name: CloudFrontDomain ALBDomain: Value: !GetAtt Alb.DNSName Export: Name: AlbDNSName
以下のコマンドを実行してデプロイしていきます。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=Vpcid,ParameterValue=作成済みのVPC ID ParameterKey=PublicSubnet1,ParameterValue=1つめのパブリックサブネットのID ParameterKey=PublicSubnet2,ParameterValue=2つめのパブリックサブネットのID --capabilities CAPABILITY_NAMED_IAM
テンプレートの説明
CloudFrontでカスタムヘッダーを設定するには229行目にあるOriginsの中で設定します。
以下のようにヘッダー名と値を設定しています。
今回は「Custom-Header」というヘッダー名で値に「kobayashi-riku0226」というものを入れています。
Origins: - CustomOriginConfig: HTTPPort: 80 OriginProtocolPolicy: http-only DomainName: !GetAtt Alb.DNSName Id: Alb OriginCustomHeaders: - HeaderName: Custom-Header HeaderValue: kobayashi-riku0226
ALBでのアクセス制限は195行目にあるリスナールールのConditionsで設定します。
また、固定レスポンスとして403を返すためにDefaultActionsで設定をしています。
AlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - FixedResponseConfig: ContentType: text/plain MessageBody: Access denied StatusCode: 403 Type: fixed-response LoadBalancerArn: !Ref Alb Port: 80 Protocol: HTTP AlbListenerRule: Type: AWS::ElasticLoadBalancingV2::ListenerRule Properties: Actions: - TargetGroupArn: !Ref TargetGroup Type: forward Conditions: - Field: http-header HttpHeaderConfig: HttpHeaderName: Custom-Header Values: - kobayashi-riku0226
動作確認
スタックの作成が完了したら以下のコマンドを実行してALBのDNS名とCloudFrontのDNS名を確認します。
aws cloudformation describe-stacks --stack-name スタック名 --query 'Stacks[0].Outputs[*].OutputValue'
実行後、2つの結果が出力されるのでそれぞれブラウザでアクセスしてみます。
以下の結果はALBのDNS名(末尾が「ap-northeast-1.elb.amazonaws.com」になっているもの)にアクセスした結果になります。
結果は画面に「Access denied」と表示されることが確認できます。
curlコマンドを使用してリクエストヘッダーに「Custom-Header: kobayashi-riku0226」指定して実行するとALBで制御されていることがわかります。
curl -H "Custom-Header: kobayashi-riku0226" http://alb-894067198.ap-northeast-1.elb.amazonaws.com -i
次にCloudFrontのDNS名(末尾が「cloudfront.net」になっているもの)にアクセスしてみます。
結果は画面に「CloudFront -> ALB -> EC2」と表示されEC2までアクセスできていることが確認できます。
この設定で気を付けることはカスタムヘッダーを外部に漏らさないようにしなければならないことです。
以下のAWSのドキュメントにも記載されていますが、カスタムヘッダーが外部に漏れた場合にCloudFrontを経由しないでオリジンに直接アクセスできるようになってしまいます。
Application Load Balancers へのアクセスを制限する
このユースケースは、カスタムヘッダー名と値の機密性維持を信頼しています。ヘッダー名と値が機密でない場合、他の HTTP クライアントは、Application Load Balancer に直接送信するリクエストにヘッダー名や値を含める可能性があります。これにより、リクエストをしていない時に、リクエストが CloudFront から送信されたかのように Application Load Balancer を動作させる可能性があります。これを防ぐためには、カスタムヘッダー名と値を機密にしておきます。
セキュリティグループにCloudFrontのマネージドプレフィックスを設定する方法
こちらの方法はALBに設定するセキュリティグループにCloudFrontのマネージドプレフィックスを設定してアクセスを制限する方法になります。
マネージドプレフィックスとは複数のCIDRをリストとして管理するものです。
これを利用することでセキュリティグループやルートテーブルで1つずつルールを設定する手間を省くことができます。
こちらもCloudFormationを使用して設定してみました。
AWSTemplateFormatVersion: "2010-09-09" Description: CloudFront ALB EC2 Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# VolumeSize: Default: 8 Type: Number Ec21InstanceType: Default: t2.micro Type: String Vpcid: Type: AWS::EC2::VPC::Id Description: Enter VPC ID PublicSubnet1: Type: AWS::EC2::Subnet::Id Description: Enter Subnet ID PublicSubnet2: Type: AWS::EC2::Subnet::Id Description: Enter Subnet ID Resources: # ------------------------------------------------------------# # IAM # ------------------------------------------------------------# Ec2SsmRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore RoleName: EC2SsmRole Ec2IamInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: Ec2InstanceProfile Roles: - !Ref Ec2SsmRole # ------------------------------------------------------------# # Security Group # ------------------------------------------------------------# AlbSg: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for ALB GroupName: alb-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - SourcePrefixListId: pl-58a04531 FromPort: 80 IpProtocol: tcp ToPort: 80 Tags: - Key: Name Value: alb-sg VpcId: !Ref Vpcid Ec2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for EC2 GroupName: ec2-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - FromPort: 80 IpProtocol: tcp ToPort: 80 SourceSecurityGroupId: !Ref AlbSg Tags: - Key: Name Value: ec2-sg VpcId: !Ref Vpcid # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# Ec2: Type: AWS::EC2::Instance Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: true Iops: 3000 VolumeSize: !Ref VolumeSize VolumeType: gp3 IamInstanceProfile: !Ref Ec2IamInstanceProfile ImageId: ami-0f36dcfcc94112ea1 InstanceType: !Ref Ec21InstanceType NetworkInterfaces: - AssociatePublicIpAddress: true DeleteOnTermination: true DeviceIndex: 0 GroupSet: - !Ref Ec2Sg SubnetId: !Ref PublicSubnet1 Tags: - Key: Name Value: ec2 UserData: !Base64 | #!/bin/bash yum update -y yum upgrade -y yum install httpd -y systemctl enable httpd systemctl start httpd touch /var/www/html/index.html echo "CloudFront -> ALB -> EC2" > /var/www/html/index.html # ------------------------------------------------------------# # ALB # ------------------------------------------------------------# Alb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 Name: alb Scheme: internet-facing SecurityGroups: - !Ref AlbSg Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 Tags: - Key: Name Value: alb Type: application TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckEnabled: true HealthCheckIntervalSeconds: 30 HealthCheckPath: / HealthCheckPort: 80 HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 5 IpAddressType: ipv4 Matcher: HttpCode: 200 Name: targetgroup Port: 80 Protocol: HTTP ProtocolVersion: HTTP1 Tags: - Key: Name Value: targetgroup Targets: - Id: !Ref Ec2 Port: 80 TargetType: instance UnhealthyThresholdCount: 2 VpcId: !Ref Vpcid AlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref Alb Port: 80 Protocol: HTTP # ------------------------------------------------------------# # CloudFront # ------------------------------------------------------------# CloudFront: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: DefaultCacheBehavior: AllowedMethods: - GET - HEAD CachedMethods: - GET - HEAD CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 TargetOriginId: Alb ViewerProtocolPolicy: allow-all Enabled: True HttpVersion: http1.1 Origins: - CustomOriginConfig: HTTPPort: 80 OriginProtocolPolicy: http-only DomainName: !GetAtt Alb.DNSName Id: Alb PriceClass: PriceClass_200 Outputs: # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# CloudFrontDomain: Value: !GetAtt CloudFront.DomainName Export: Name: CloudFrontDomain ALBDomain: Value: !GetAtt Alb.DNSName Export: Name: AlbDNSName
CloudFrontでカスタムヘッダーをつけてALBで制限する方法と同じコマンドでデプロイ、DNS名の確認をします。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=Vpcid,ParameterValue=作成済みのVPC ID ParameterKey=PublicSubnet1,ParameterValue=1つめのパブリックサブネットのID ParameterKey=PublicSubnet2,ParameterValue=2つめのパブリックサブネットのID --capabilities CAPABILITY_NAMED_IAM aws cloudformation describe-stacks --stack-name スタック名 --query 'Stacks[0].Outputs[*].OutputValue'
テンプレート説明
一番上のテンプレートとほとんど同じ内容ですが、59行目のALBのセキュリティグループでCloudFrontのマネージドプレフィックスリストを設定しています。
SourcePrefixListIdでマネージドプレフィックスリストのIDを指定します。
AlbSg: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for ALB GroupName: alb-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - SourcePrefixListId: pl-58a04531 FromPort: 80 IpProtocol: tcp ToPort: 80 Tags: - Key: Name Value: alb-sg VpcId: !Ref Vpcid
動作確認
ALBのDNS名とCloudFrontのDNS名にブラウザからアクセスしてみます。
ALBの場合は以下の画像のようにタイムアウトになりアクセスに失敗します。
CloudFrontの場合は以下のように接続に成功します。
この設定で気を付けることは以下のドキュメントの通りCloudFrontのマネージドプレフィックスは重みが55なので55個分のルールが設定されるのと同等になります。
デフォルトのセキュリティグループのルール数だと60個までしかルールが設定できないので60個以上設定したい場合は上限緩和で対応する必要があります。
AWS マネージドプレフィックスリストのウェイト
さいごに
設定はどちらも難しくはないですが、カスタムヘッダーを漏洩させないように管理するよりはマネージドプレフィックスリストを設定する方が良いのかなとは思いました。